Estudiante: Judit Lozano Gondolbeu
El dataset revela el número de accidentes de tráfico ocurridos en Canadá desde el año 1999 hasta el 2014.
El objetivo de este estudio es encontrar un modelo que prediga, dado un conductor que sufre un accidente, si habrá fallecimientos o no.
Este cálculo determinará el capital preciso para que las compañias aseguradoras en este caso puedan hacer frente a las coberturas indemnizatorias por tales siniestros.
La dotación errónea de esa partida afectaría a la cuenta empresarial de resultados. Por exceso, al distorsionar la cifra de gastos del balance, inmovilizando recursos para atender inversiones rentables. Por defecto, al generar desajustes de tesorería, con las consiguientes consecuencias jurídicas derivadas de situaciones de iliquidez (concurso de acreedores) o ejecuciones judiciales (embargos). Y todo ello al margen de las sanciones administrativas (multas) por parte de la dirección general de seguros al incumplir la normativa sobre provisiones técnicas.
!Conda info
active environment : practica_ml
active env location : /Users/JuditLozano/opt/anaconda3/envs/practica_ml
shell level : 2
user config file : /Users/JuditLozano/.condarc
populated config files : /Users/JuditLozano/.condarc
conda version : 4.10.3
conda-build version : 3.21.6
python version : 3.8.12.final.0
virtual packages : __osx=10.16=0
__unix=0=0
__archspec=1=x86_64
base environment : /Users/JuditLozano/opt/anaconda3 (writable)
conda av data dir : /Users/JuditLozano/opt/anaconda3/etc/conda
conda av metadata url : None
channel URLs : https://repo.anaconda.com/pkgs/main/osx-64
https://repo.anaconda.com/pkgs/main/noarch
https://repo.anaconda.com/pkgs/r/osx-64
https://repo.anaconda.com/pkgs/r/noarch
package cache : /Users/JuditLozano/opt/anaconda3/pkgs
/Users/JuditLozano/.conda/pkgs
envs directories : /Users/JuditLozano/opt/anaconda3/envs
/Users/JuditLozano/.conda/envs
platform : osx-64
user-agent : conda/4.10.3 requests/2.26.0 CPython/3.8.12 Darwin/20.5.0 OSX/10.16
UID:GID : 501:20
netrc file : /Users/JuditLozano/.netrc
offline mode : False
#primero todas las librerías porbloques
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
import plotly.express as px
from dython.nominal import associations
import warnings
warnings.filterwarnings("ignore")
from IPython.display import Image
#opciones de visualización
pd.set_option('display.max_columns', 500)
pd.set_option('display.max_rows', 5000)
def duplicate_columns(frame):
groups = frame.columns.to_series().groupby(frame.dtypes).groups
dups = []
for t, v in groups.items():
cs = frame[v].columns
vs = frame[v]
lcs = len(cs)
for i in range(lcs):
ia = vs.iloc[:,i].values
for j in range(i+1, lcs):
ja = vs.iloc[:,j].values
if np.array_equal(ia, ja):
dups.append(cs[i])
break
return dups
def plot_feature(col_name, isNumeric):
"""
Visualize a variable with and without faceting on the fatalities yes/no
- col_name is the variable name
- numeric is True if the variable is numeric, False otherwise
"""
f, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(12,3), dpi=90)
# Plot without TARGET
if isNumeric:
sns.distplot(df_data.loc[df_data[col_name].notnull(), col_name], kde=False, ax=ax1)
else:
sns.countplot(df_data[col_name], order=sorted(df_data[col_name].unique()), color='#5975A4', saturation=1, ax=ax1, hue=df_data['TARGET'], palette = "Set2")
ax1.set_xlabel(col_name)
ax1.set_ylabel('Count')
ax1.set_title("Conductores involucrados en accidentes totales")
# Plot with TARGET
if isNumeric:
sns.kdeplot(x=df_data[col_name].loc[df_data['TARGET']== 0],
shade= True, label='no_fatalities')
sns.kdeplot(x=df_data[col_name].loc[df_data['TARGET']== 1],
shade= True, label='fatalities')
plt.legend()
ax2.set_ylabel('')
ax2.set_title('Distribución segun '+col_name)
else:
plt_data = df_data.groupby(col_name)['TARGET'].value_counts(normalize=True).to_frame('proportion').reset_index()
sns.barplot(x = col_name, y = 'proportion', hue= "TARGET", data = plt_data, saturation=1, ax=ax2)
ax2.set_ylabel('TARGET fraction')
ax2.set_xlabel(col_name)
ax2.set_title('% fatalidad entre los accidentes segun '+col_name)
plt.tight_layout()
def plot_feature_fatal(col_name):
f, ax = plt.subplots()
sns.countplot(df_data[df_data['TARGET']== 1][col_name], order=sorted(df_data[df_data['TARGET']== 1][col_name].unique()), color='#5975A4', saturation=1, palette = "Set2")
ax.set_xlabel(col_name)
ax.set_ylabel('Count')
ax.set_title("Conductores involucrados en accidentes mortales")
plt.tight_layout()
list_columns = ['C_YEAR', 'C_MNTH', 'C_WDAY', 'C_HOUR', 'C_SEV', 'C_CONF',
'C_RCFG', 'C_WTHR', 'C_RSUR', 'C_RALN', 'C_TRAF', 'V_ID', 'V_TYPE',
'V_YEAR', 'P_ID', 'P_SEX', 'P_AGE', 'P_PSN', 'P_ISEV', 'P_SAFE',
'P_USER']
data = pd.read_csv("../data/NCDB_1999_to_2014.csv").loc[:, list_columns]
data.shape
(5860405, 21)
data.head()
| C_YEAR | C_MNTH | C_WDAY | C_HOUR | C_SEV | C_CONF | C_RCFG | C_WTHR | C_RSUR | C_RALN | C_TRAF | V_ID | V_TYPE | V_YEAR | P_ID | P_SEX | P_AGE | P_PSN | P_ISEV | P_SAFE | P_USER | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1999 | 1 | 1 | 20 | 2 | 34 | UU | 1 | 5 | 3 | 03 | 01 | 06 | 1990 | 01 | M | 41 | 11 | 1 | UU | 1 |
| 1 | 1999 | 1 | 1 | 20 | 2 | 34 | UU | 1 | 5 | 3 | 03 | 02 | 01 | 1987 | 01 | M | 19 | 11 | 1 | UU | 1 |
| 2 | 1999 | 1 | 1 | 20 | 2 | 34 | UU | 1 | 5 | 3 | 03 | 02 | 01 | 1987 | 02 | F | 20 | 13 | 2 | 02 | 2 |
| 3 | 1999 | 1 | 1 | 08 | 2 | 01 | UU | 5 | 3 | 6 | 18 | 01 | 01 | 1986 | 01 | M | 46 | 11 | 1 | UU | 1 |
| 4 | 1999 | 1 | 1 | 08 | 2 | 01 | UU | 5 | 3 | 6 | 18 | 99 | NN | NNNN | 01 | M | 05 | 99 | 2 | UU | 3 |
Image('../images/diccionari_datos.png')
Observamos los tipos de las variables del dataset y el nombre de las columnas
data.dtypes
C_YEAR int64 C_MNTH object C_WDAY object C_HOUR object C_SEV int64 C_CONF object C_RCFG object C_WTHR object C_RSUR object C_RALN object C_TRAF object V_ID object V_TYPE object V_YEAR object P_ID object P_SEX object P_AGE object P_PSN object P_ISEV object P_SAFE object P_USER object dtype: object
df_data = data
Nos hacemos una idea del tipo de valores unicos que tenemos en cada columna y observamos que son etiquetas, es decir no son valores numéricos que se puedan fraccionar. Por lo tanto, pasamos a unificar los valores de las variables a enteros para facilitar el procesado de datos.
for col in df_data:
print(df_data[col].name, df_data[col].unique())
C_YEAR [1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014] C_MNTH [1 2 3 4 5 6 7 8 9 10 11 12 '12' 'UU' '01' '02' '11'] C_WDAY [1 2 3 4 5 6 7 '7' 'U' '1' '2' '3' '4' '5' '6'] C_HOUR ['20' '08' '17' '15' '14' '01' '11' '13' '19' '16' '09' '02' '18' '12' '10' '23' '00' '06' '07' '21' 'UU' '05' '22' '03' '04'] C_SEV [2 1] C_CONF ['34' '01' 'QQ' '04' '31' '21' '23' '03' '02' '33' 'UU' '24' '35' '41' '06' '32' '36' '05' '22' '25'] C_RCFG ['UU' 'QQ' '01' '02' '03' '05' '04' '06' '08' '07' '09' '10'] C_WTHR ['1' '5' '3' '4' '7' '2' 'U' '6' 'Q'] C_RSUR ['5' '3' '2' '4' '1' '6' 'U' 'Q' '7' '9' '8'] C_RALN ['3' '6' '1' 'U' '2' '5' '4' 'Q'] C_TRAF ['03' '18' '01' 'UU' '06' '10' '05' '04' '11' 'QQ' '07' '08' '16' '17' '02' '13' '15' '09' '12'] V_ID ['01' '02' '99' '03' '04' 'UU' '05' '06' '07' '08' '09' '10' '11' '12' '13' '14' '15' '16' '17' '18' '19' '20' '21' '22' '23' '24' '25' '26' '27' '28' '29' '30' '31' '32' '33' '34' '35' '36' '37' '38' '39' '40' '41' '42' '43' '44' '45' '46' '47' '48' '49' '50' '51' '52' '53' '54' '55' '56' '57' '58' '59' '60' '61' '62' '63' '64' '65' '66' '67' '68' '69' '70' '71' 2 1 99 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 '72' '73' '74' '83' '85' '86' '75' '76' '77' 42 43 44 45 46 47 48 49 51 52 50 53 54 55 56 57] V_TYPE ['06' '01' 'NN' '11' 'UU' '20' '17' '07' '08' 'QQ' '09' '22' '14' '23' '05' '16' '19' '18' '10' '21'] V_YEAR ['1990' '1987' '1986' 'NNNN' '1984' '1991' '1992' '1997' '1993' '1985' '1988' '1994' '1995' '1998' '1989' 'UUUU' '1996' '1983' '1999' '1965' '1977' '1978' '1968' '1981' '1979' '1976' '1972' '2000' '1982' '1975' '1973' '1974' '1980' '1967' '1970' '1971' '1962' '1969' '1966' '1945' '1963' '1960' '1950' '1964' '1959' '1955' '1958' '1903' '1909' '1949' '1923' '1961' '1914' '1908' '1953' '1906' '1939' '1925' '1948' '1938' '1907' '1952' '1904' '1917' '1912' '1944' '1956' '1930' '1931' '1951' '1946' '1947' '1957' '1954' '1943' '1901' '1937' '1905' '1935' '1926' '1941' '1932' '1920' '1933' '1919' '1915' '1929' '1928' '2001' '1913' '1940' '1927' '2002' '1916' '1942' '1918' '2003' '1924' '1922' '1934' '2004' '2005' '2006' '2007' '2008' '1911' '2009' '2010' '2011' '1936' '2012' '1910' '1921' '2013' '2014' '2015'] P_ID ['01' '02' '03' '04' '05' '06' 'NN' '07' '08' '09' '10' '11' '12' '13' '14' '15' '16' '17' '18' '19' '20' '21' '22' '23' '24' '25' '26' '27' '28' '29' '30' '31' '32' '33' '34' '35' '36' '37' '38' '39' '40' '41' '42' '43' '44' '45' '46' '47' '48' '49' '50' '51' '52' '53' '54' '55' '56' '57' '58' '59' '60' '61' '62' '63' '64' '65' '66' '67' '68' '69' '70' '71' '72' '73' '74' '75' '76' '77' '78' '79' '80' '81' '82' '83' '84' '85' '86' '87' '88' '89' '90' '91' '92' '93' '94' '95' 'UU' '99'] P_SEX ['M' 'F' 'U' 'N'] P_AGE ['41' '19' '20' '46' '05' '28' '21' 'UU' '61' '56' '34' '22' '30' '49' '32' '31' '68' '08' '45' '17' '33' '82' '39' '37' '55' '38' '43' '35' '23' '25' '65' '44' '36' '70' '50' '40' '27' '26' '15' '53' '16' '13' '14' '12' '18' '77' '86' '42' '24' '47' '62' '06' '57' '83' '74' '67' '51' '29' '01' '02' '54' '71' '10' '79' '63' '58' '48' '60' '07' '64' '75' '52' '85' '93' '92' '69' '72' '11' '59' '09' '66' '76' '73' '04' '78' '80' '84' '03' '81' '89' '87' '88' '90' 'NN' '91' '95' '97' '94' '99' '98' '96'] P_PSN ['11' '13' '99' '23' '98' '21' '22' '12' 'QQ' '96' '32' 'UU' 'NN' '31' '33' '97'] P_ISEV ['1' '2' '3' 'U' 'N'] P_SAFE ['UU' '02' 'NN' '01' '13' '12' '09' 'QQ' '10' '11'] P_USER ['1' '2' '3' 'U' '4' '5']
# los valores desconocidos tipo "UU" los pasaremos a numeros lejanos según los valoren que conformen esa variable
# esto es para su clara visualización y localización a la hora de generar los gráficos
unknowns_a = {'UU' : 50, 'XX' : 51, 'U' : 52, 'X' : 53, 'NN' : 54, 'Q' : 55, 'QQ' : 56}
unknowns_year = { 'UUUU' : 2100,'XXXX' : 2101, 'NNNN' : 2102}
unknowns_age = {'UU' : 150, 'XX' : 151, 'NN' : 152, 'QQ' : 153}
unknowns_sex = {'U' : 8, 'N' : 9, 'M' : 0, 'F' : 1}
df_data.columns
Index(['C_YEAR', 'C_MNTH', 'C_WDAY', 'C_HOUR', 'C_SEV', 'C_CONF', 'C_RCFG',
'C_WTHR', 'C_RSUR', 'C_RALN', 'C_TRAF', 'V_ID', 'V_TYPE', 'V_YEAR',
'P_ID', 'P_SEX', 'P_AGE', 'P_PSN', 'P_ISEV', 'P_SAFE', 'P_USER'],
dtype='object')
col_cat_a = ['C_MNTH', 'C_WDAY', 'C_HOUR', 'C_CONF',
'C_RCFG', 'C_WTHR', 'C_RSUR', 'C_RALN', 'C_TRAF', "V_TYPE", "P_SAFE", "P_PSN"]
col_cat_year = ["V_YEAR", 'C_YEAR']
col_cat_age = ["P_AGE","P_ID", "V_ID"]
col_cat_sex = ["P_SEX","P_USER","P_ISEV"]
#lo pasamos todo a entero para que no haya mezclas de strings y numeros en las columnas (ej: no queremos '1' y 1)
df_col_a = df_data[col_cat_a].replace(unknowns_a).astype(int)
df_col_year = df_data[col_cat_year].replace(unknowns_year).astype(int)
df_col_age = df_data[col_cat_age].replace(unknowns_age).astype(int)
df_col_sex = df_data[col_cat_sex].replace(unknowns_sex).astype(int)
df_data = pd.concat([df_col_a, df_col_year, df_col_age, df_col_sex, df_data['C_SEV']], axis=1).reset_index(drop=True)
df_data.shape
(5860405, 21)
En el dataset cada fila corresponde a una persona que estuvo involucrada en un accidente. Es decir, cada registro puede hacer referencia al conductor, a alguien que viajaba en la segunda fila o incluso a un peatón que pasaba por alli.
Dado que el objetivo de este estudio es centrarse en los conductores desde el punto de vista de las compañias aseguradoras, crearemos una nueva variable que identifique a los conductores según la clase 'driver' de la variable P_PSN.
#identifico las filas de tipo 'conductor'
df_data['DRIVERS'] = np.where(df_data['P_PSN']==11,1,0) #identifico las filas de tipo 'conductor'
#identifico con un unico ID las filas pertenecientes al mismo accidente
df_data['ACCIDENT_ID'] = df_data.groupby(['C_YEAR', 'C_MNTH', 'C_WDAY', 'C_HOUR', 'C_SEV', 'C_CONF',
'C_RCFG', 'C_WTHR', 'C_RSUR', 'C_RALN', 'C_TRAF']).ngroup()
Para comprobar que esta interpretación es correcta, cogeremos la documentación y comparamos el numero de accidentes segun nuestra interpretación del dataset con los datos concretos que figuran en la documentacion:
mask_fall = df_data['C_SEV']==1 #filtramos por accidentes con fallecidos
print("Total numero de accidentes con algun fallecido según lectura del dataset: ", len(df_data[mask_fall].groupby('ACCIDENT_ID')))
Total numero de accidentes con algun fallecido según lectura del dataset: 35802
Segun la documentación, seleccionando el periodo que nos interesa (del 1999 al 2014):
Accidentes con resultado mortal : 36000
Image('../images/collisions.png')
Como observamos la lectura de los datos se aproxima bastante a la documentación. La diferencia de menos de 200 casos (menor del 1%) puede explicarse a la actualización de los datos que figuran en el pdf o en el csv o en ambos. Al ser la diferencia menor del 1%, concluiremos que la interpretación de la lectura de los datos en correcta.
Acontinuación introduciremos nuevas variables que puedan aportar valor al análisis como por ejemplo número de ocupantes por vehículo en el momento del accidente. Este dato podría ser útil si el asegurado en el momento de contratar la póliza indicara el número de ocupantes que espera llevar en el coche a diario de media.
#identifico el numero de personas que viajaba en el mismo coche
df_data['PP_CAR'] = df_data.groupby(['ACCIDENT_ID','V_ID']).ngroup()
df_data['PP_CAR'] = df_data.groupby('PP_CAR')['PP_CAR'].transform('count') #contamos
Observamos la frecuencia de datos en nuestra variable creada sobre el número de occupantes filtrando por conductores (entendiendo que 1 conductor == 1 vehiculo) y filtrando por los casos donde hubo accidentes mortales:
mask_driv = df_data['DRIVERS']==1
df_data[mask_fall& mask_driv]['PP_CAR'].value_counts(normalize=True)
1 0.653950 2 0.214959 3 0.067976 4 0.037817 5 0.015158 6 0.005280 7 0.002168 8 0.001066 9 0.000332 13 0.000210 10 0.000175 11 0.000122 12 0.000105 26 0.000052 43 0.000052 15 0.000052 14 0.000052 25 0.000052 23 0.000052 19 0.000035 16 0.000035 29 0.000035 18 0.000035 42 0.000035 36 0.000017 31 0.000017 52 0.000017 32 0.000017 21 0.000017 17 0.000017 39 0.000017 35 0.000017 49 0.000017 34 0.000017 22 0.000017 Name: PP_CAR, dtype: float64
De este análisis se entiende que la mayoría de los casos donde hubo algún fallecido el conductor iba solo (65%), en un 22% de los casos en el vehículo iban 2 personas. Procederemos a agrupar el resto de opciones en una categoría nueva "más de dos" , correspondiente a la etiqueta==3:
df_data['PP_CAR'] = np.where(df_data['PP_CAR']>2,3,df_data['PP_CAR'])
df_data[mask_fall& mask_driv]['PP_CAR'].value_counts(normalize=True)
1 0.653950 2 0.214959 3 0.131091 Name: PP_CAR, dtype: float64
A continuación, filtraremos fuera del dataset a los no conductores y crearemos nuestra variable 'Target' que identifica a los conductores involucrados en un accidente donde hubo algún fallecido con un 1 y conductores que tuvieron un accidente pero sin fallecidos con un 0.
Además dejaremos fuera del data set las variables que no vamos a utiliar como por ejemplo: ['C_CONF', 'P_PSN', 'P_ID', 'V_ID','P_USER',"P_ISEV", 'C_SEV', 'ACCIDENT_ID', 'DRIVERS']
Tal exclusión obedece bien a que no aportan valor (como los IDs), bien a que reflejan datos inútiles para la predicción. Y ello, al no estar disponibles con antelación a la producción de un accidente del asegurado. Es decir, al momento de la implantación del modelo pretendido.
mask_drivers = df_data['DRIVERS']==1 #creo una máscara con las filas de tipo 'conductor'
df_data = df_data[mask_drivers] #filtro para ver solo los registros de tipo conductor
df_data['TARGET'] = np.where(df_data['C_SEV']==1,1,0) #identifico a los conductores involucrados en un accidente donde hubo algún fallecido
df_data.columns
Index(['C_MNTH', 'C_WDAY', 'C_HOUR', 'C_CONF', 'C_RCFG', 'C_WTHR', 'C_RSUR',
'C_RALN', 'C_TRAF', 'V_TYPE', 'P_SAFE', 'P_PSN', 'V_YEAR', 'C_YEAR',
'P_AGE', 'P_ID', 'V_ID', 'P_SEX', 'P_USER', 'P_ISEV', 'C_SEV',
'DRIVERS', 'ACCIDENT_ID', 'PP_CAR', 'TARGET'],
dtype='object')
df_data = df_data.drop(['C_CONF', 'P_PSN', 'P_ID', 'V_ID','P_USER',"P_ISEV", 'C_SEV', 'ACCIDENT_ID', 'DRIVERS'], axis= 1)
df_data.shape
(3926086, 16)
Aqui se observa un desbalanceo significanivo de los datos ya que tan solo un 1.46% de los conductores ha estado involucrado en algun accidente donde hubo al menos un fallecido. Este desbalanceo se tratará más adelante.
df_plot_target = df_data['TARGET'] \
.value_counts(normalize=True)\
.mul(100).rename('percent').reset_index()
df_plot_conteo = df_data['TARGET'].value_counts().rename('conteo').reset_index()
df_plot_fatalities = pd.merge(df_plot_target, df_plot_conteo, on=['index'], how='inner')
df_plot_fatalities
| index | percent | conteo | |
|---|---|---|---|
| 0 | 0 | 98.543155 | 3868889 |
| 1 | 1 | 1.456845 | 57197 |
fig = px.histogram(df_plot_fatalities, x='index', y='percent')
fig.update_xaxes(type='category')
fig.show()
Observamos que no hay ninguna columna duplicada en el dataframe.
duplicate_cols = duplicate_columns(df_data)
duplicate_cols
[]
Observamos que no hay valores nulos en el dataset.
#analizarnulos
df_dtypes = pd.merge(df_data.isnull().sum(axis = 0).sort_values().to_frame('missing_value').reset_index(),
df_data.dtypes.to_frame('feature_type').reset_index(),
on = 'index', how = 'inner')
df_dtypes
| index | missing_value | feature_type | |
|---|---|---|---|
| 0 | C_MNTH | 0 | int64 |
| 1 | C_WDAY | 0 | int64 |
| 2 | C_HOUR | 0 | int64 |
| 3 | C_RCFG | 0 | int64 |
| 4 | C_WTHR | 0 | int64 |
| 5 | C_RSUR | 0 | int64 |
| 6 | C_RALN | 0 | int64 |
| 7 | C_TRAF | 0 | int64 |
| 8 | V_TYPE | 0 | int64 |
| 9 | P_SAFE | 0 | int64 |
| 10 | V_YEAR | 0 | int64 |
| 11 | C_YEAR | 0 | int64 |
| 12 | P_AGE | 0 | int64 |
| 13 | P_SEX | 0 | int64 |
| 14 | PP_CAR | 0 | int64 |
| 15 | TARGET | 0 | int64 |
# nos guardamos la tabla a CSV
df_data.to_csv('../data/df_data.csv', index=False)
df_data.head()
| C_MNTH | C_WDAY | C_HOUR | C_RCFG | C_WTHR | C_RSUR | C_RALN | C_TRAF | V_TYPE | P_SAFE | V_YEAR | C_YEAR | P_AGE | P_SEX | PP_CAR | TARGET | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 1 | 20 | 50 | 1 | 5 | 3 | 3 | 6 | 50 | 1990 | 1999 | 41 | 0 | 1 | 0 |
| 1 | 1 | 1 | 20 | 50 | 1 | 5 | 3 | 3 | 1 | 50 | 1987 | 1999 | 19 | 0 | 2 | 0 |
| 3 | 1 | 1 | 8 | 50 | 5 | 3 | 6 | 18 | 1 | 50 | 1986 | 1999 | 46 | 0 | 1 | 0 |
| 5 | 1 | 1 | 17 | 56 | 1 | 2 | 1 | 1 | 1 | 50 | 1984 | 1999 | 28 | 0 | 1 | 0 |
| 6 | 1 | 1 | 17 | 56 | 1 | 2 | 1 | 1 | 1 | 50 | 1991 | 1999 | 21 | 0 | 2 | 0 |
df_data.columns
Index(['C_MNTH', 'C_WDAY', 'C_HOUR', 'C_RCFG', 'C_WTHR', 'C_RSUR', 'C_RALN',
'C_TRAF', 'V_TYPE', 'P_SAFE', 'V_YEAR', 'C_YEAR', 'P_AGE', 'P_SEX',
'PP_CAR', 'TARGET'],
dtype='object')
df_cat = df_data[['C_MNTH', 'C_WDAY', 'C_HOUR', 'C_RCFG', 'C_WTHR', 'C_RSUR', 'C_RALN',
'C_TRAF', 'V_TYPE', 'P_SAFE', 'P_SEX']]
#Visualización variables categóricas
for col in df_cat:
plot_feature(col, False)
#Visualización filtrando por conductores involucrados en accidentes mortales sólo
for col in df_cat:
plot_feature_fatal(col)
Meses
El número de conductores accidentados declina a partir de enero pero sube a partir de marzo coincidiendo con la llegada de la primavera en Canadá y se mantiene aproximadamente constante durante el resto del año. Analizando el numero de accidentados por meses, la proporción de accidentes mortales es ligeramente más alta en julio y agosto coincidiendo con los meses más cálidos en Canadá.
Días de la semana
El viernes es el día de la semana donde se concentran el mayor número de accidentes. La proporción de accidentes mortales entre los accidentes que ocurren a diario es más alta los sábados y domingos.
Horario
La hora del día donde los accidentes ocurren con más frecuencia es entre las 3 y las 5 de la tarde. Sin embargo la proporción más alta de mortalidad por horas se concentra entre las 12 y las 7 de la madrugada.
Configuración de la carretera y alineación de la carretera
El mayor número de accidentes ocurre en los puntos con intersecciones de al menos dos carreteras o entre bloques en lía recta. Sin embargo, la proporción de mortalidad entre los accidentes es mayor en las carretas con algún tipo de desnivel.
Esta categoría se corresponden bastante bien con los datos que aparecen según la alineación de la carretera. Según estos datos, la mayoría de los accidentes ocurren cuando la carretera es recta . Sin embargo, en proporción, la mortalidad se concentraría más ligeramente en los casos donde existe alguna curva y desnivel.
Señalización de la carretera
El número de conductores accidentados se disparan cuando la carretera no tiene ningún tipo de señalización. Sin embargo, en los pasos de trenes donde hay algún tipo de señalización la proporción de mortalidad entre los accidentes es más elevada.
Tipo de vehículo
El tipo de vehículo más accidentado es el turismo o furgoneta ligera o vehículo ligero. Sin embargo, el tipo de vehículo con mortalidad más alta en proporción son de tipo tractor y los vehículos de nieve.
Dispositivo de seguridad
La mayoría de los accidentados usó algún tipo de dispositivo de seguridad. Proporcionalmente se cumple que los casos mortales son más altos cuando no existió ningún tipo de dispositivo de seguridad.
Sexo
Hay un mayor número de conductores accidentados hombres en total. Sin embargo, en proporción solo ligeramente los hombres superan a las mujeres en el numero de casos mortales.
Climatología
En cuanto al tiempo, la mayoría de accidentes ocurre cuando hace bueno pero la mortalidad se dispara cuando la visibilidad es limitada (niebla, humo, polvo etc…)
Superficie de la calzada
Asimismo, la mayoría de accidentes ocurren en superficie normal/ seca aunque en proporción la mortalidad se concentra más en los casos cuando hay agua en la calzada.
#Visualización variables numericas
df_num = df_data[['V_YEAR', 'P_AGE', 'PP_CAR']]
for col in df_num:
plot_feature(col, True)
# Plot boxplots
for col in df_num:
f, ax1 = plt.subplots(figsize=(12,3), dpi=90)
ax = sns.boxplot(x=col, y='TARGET', data=df_data, orient="h")
ax.get_yaxis().set_visible(True)
En relación a las variables numéricas se puede visualizar los outliers o puntos desconocidos más alejados de la muestra principal. En este caso, decidimos no eliminar los outliers para no perder información.
Se observa una distribución similar en el caso del año del vehículo para casos con o sin fallecidos, siendo la moda del año del vehículo entorno al 2000.
Igualmente se observa una distribución similar en el caso de la edad de los conductores para casos con o sin fallecidos. También aquí se observan dos modas entorno a los 18-20 años de edad y entorno a los 40.
En cuanto al número de ocupantes se observa que la mayoría de conductores accidentados ocurre cuando éste va sólo y además en proporción, la concentración de casos mortales también ocurre cuando el conductor va sólo.
# Plot features associations
associations(df_data, nom_nom_assoc='cramer', figsize=(15, 15))
{'corr': C_MNTH C_WDAY C_HOUR C_RCFG C_WTHR C_RSUR C_RALN \
C_MNTH 1.000000 0.020906 0.020242 -0.002399 -0.007910 -0.040358 -0.004737
C_WDAY 0.020906 1.000000 0.006097 0.002401 0.000589 -0.003143 -0.005046
C_HOUR 0.020242 0.006097 1.000000 0.006172 0.006222 -0.009705 0.004767
C_RCFG -0.002399 0.002401 0.006172 1.000000 0.155620 0.225975 0.641408
C_WTHR -0.007910 0.000589 0.006222 0.155620 1.000000 0.418858 0.269157
C_RSUR -0.040358 -0.003143 -0.009705 0.225975 0.418858 1.000000 0.328008
C_RALN -0.004737 -0.005046 0.004767 0.641408 0.269157 0.328008 1.000000
C_TRAF 0.002205 0.015145 -0.010318 0.201545 0.161749 0.119248 0.129725
V_TYPE 0.009132 -0.003745 0.011106 -0.000122 0.010652 -0.012018 -0.025891
P_SAFE 0.003117 0.014274 0.022120 0.004052 0.012922 -0.037260 -0.046449
V_YEAR 0.021267 -0.009257 0.019838 0.028137 -0.004945 -0.030596 -0.044936
C_YEAR -0.000029 -0.014576 -0.011822 -0.034953 -0.012367 0.006406 -0.036166
P_AGE 0.006989 -0.017275 0.001400 -0.011292 -0.000413 -0.025706 -0.030047
P_SEX 0.005964 -0.007351 0.011532 0.037533 0.005365 -0.012323 -0.015182
PP_CAR 0.038303 0.030660 0.039830 -0.130739 -0.095100 -0.098836 -0.096001
TARGET 0.005711 0.014955 -0.011803 -0.008193 -0.003058 0.000565 -0.016882
C_TRAF V_TYPE P_SAFE V_YEAR C_YEAR P_AGE P_SEX \
C_MNTH 0.002205 0.009132 0.003117 0.021267 -0.000029 0.006989 0.005964
C_WDAY 0.015145 -0.003745 0.014274 -0.009257 -0.014576 -0.017275 -0.007351
C_HOUR -0.010318 0.011106 0.022120 0.019838 -0.011822 0.001400 0.011532
C_RCFG 0.201545 -0.000122 0.004052 0.028137 -0.034953 -0.011292 0.037533
C_WTHR 0.161749 0.010652 0.012922 -0.004945 -0.012367 -0.000413 0.005365
C_RSUR 0.119248 -0.012018 -0.037260 -0.030596 0.006406 -0.025706 -0.012323
C_RALN 0.129725 -0.025891 -0.046449 -0.044936 -0.036166 -0.030047 -0.015182
C_TRAF 1.000000 0.047986 0.029835 0.031348 0.010278 -0.013281 0.003526
V_TYPE 0.047986 1.000000 0.170777 0.348722 -0.002665 0.174033 0.123455
P_SAFE 0.029835 0.170777 1.000000 0.239227 -0.038957 0.254749 0.244227
V_YEAR 0.031348 0.348722 0.239227 1.000000 0.140847 0.348406 0.304643
C_YEAR 0.010278 -0.002665 -0.038957 0.140847 1.000000 0.038258 0.008871
P_AGE -0.013281 0.174033 0.254749 0.348406 0.038258 1.000000 0.521240
P_SEX 0.003526 0.123455 0.244227 0.304643 0.008871 0.521240 1.000000
PP_CAR -0.149076 -0.061349 -0.108829 -0.080638 -0.033926 -0.071025 -0.066936
TARGET 0.035952 0.019085 -0.005245 -0.007264 -0.005188 -0.005231 -0.021907
PP_CAR TARGET
C_MNTH 0.038303 0.005711
C_WDAY 0.030660 0.014955
C_HOUR 0.039830 -0.011803
C_RCFG -0.130739 -0.008193
C_WTHR -0.095100 -0.003058
C_RSUR -0.098836 0.000565
C_RALN -0.096001 -0.016882
C_TRAF -0.149076 0.035952
V_TYPE -0.061349 0.019085
P_SAFE -0.108829 -0.005245
V_YEAR -0.080638 -0.007264
C_YEAR -0.033926 -0.005188
P_AGE -0.071025 -0.005231
P_SEX -0.066936 -0.021907
PP_CAR 1.000000 -0.047262
TARGET -0.047262 1.000000 ,
'ax': <AxesSubplot:>}
La matriz de Cramers muestra como no hay un ningún par de variables con una alta asociación en el dataset. Las únicas variables que guardarían cierta relación sin llegar a ser muy altas son las variables Road Aligment y Road configuration que ya discutimos en el apartado anterior.
df_data.columns
Index(['C_MNTH', 'C_WDAY', 'C_HOUR', 'C_RCFG', 'C_WTHR', 'C_RSUR', 'C_RALN',
'C_TRAF', 'V_TYPE', 'P_SAFE', 'V_YEAR', 'C_YEAR', 'P_AGE', 'P_SEX',
'PP_CAR', 'TARGET'],
dtype='object')
# Creating a matrix using age, salry, balance as rows and columns
df_data[['P_AGE','TARGET','V_YEAR', 'C_YEAR','PP_CAR']].corr()
#plot the correlation matrix of salary, balance and age in data dataframe.
sns.heatmap(df_data[['P_AGE','TARGET','V_YEAR', 'C_YEAR','PP_CAR']].corr(), annot=True, cmap = 'Reds')
plt.show()
Se observa poca correlacion entre las variables numéricas de la muestra.
De entre ellas, la más alta correlacion es entre el año de matriculación del coche accidentado (V_YEAR) y la edad del conductor (P_AGE) pero como observamos al no ser muy alta no vamos a proceder a la eliminación de ninguna variable.
En relación a nuestra variable target (TARGET), la variable con la correlacion mas alta correspondería a la del numero de personas que viajaban en el coche en el momento del accidente (PP_CAR). De nuevo, al ser la correlación muy pequeña, concluiremos que ésta es insignificante.